using System;
using System.Collections.Generic;
using System.Reflection.Emit;
using System.Text;

namespace CoreCalc.SheetDefinedFunctions.CompOnlyNeededCells
{
    public abstract class ConditionTreeNode : CGExpr
    {
        /// <summary>
        /// Puts NaN on the stack
        /// </summary>
        /// <param name="ilg"></param>
        protected static Generate OnError(ILGenerator ilg)
        {
            return new Generate(delegate { ilg.Emit(OpCodes.Ldc_R8, double.NaN); });
        }

        public override void Compile(ILGenerator ilg)
        {
            throw new System.InvalidOperationException();
        }

        public override void CompileToDouble(ILGenerator ilg, Generate g, Generate onError)
        {
            CompileToDouble(ilg, g);
        }

        public override void CompileToDoubleProper(ILGenerator ilg, Generate ifProper, Generate ifOther)
        {
          throw new Exception("Not implemented/sestoft.");
        }

        public override void CompileCondition(ILGenerator ilg, Generate ifTrue, Generate ifFalse, Generate ifOther)
        {
          throw new Exception("Not implemented/sestoft.");
        }

        public override StrictCellTypeHolder GetStrictCells()
        {
            throw new System.InvalidOperationException();
        }

        public override Typ Type()
        {
            throw new System.InvalidOperationException();
        }

        public override ConditionDictionary GetConditions()
        {
            throw new System.InvalidOperationException();
        }
    }

    public class ConditionTreeNodeAnd : ConditionTreeNode
    {
        private readonly And andFunction = And.instance;
        private readonly ConditionTreeNode[] expressions;

        public ConditionTreeNodeAnd(ConditionTreeNode firstExpr, ConditionTreeNode sndExpr)
        {
            expressions = new ConditionTreeNode[2];
            expressions[0] = firstExpr;
            expressions[1] = sndExpr;
        }
        
        public override void CompileToDouble(ILGenerator ilg, Generate onSuccess)
        {
            // sestoft: changed
            andFunction.CompileToDouble(ilg, onSuccess, OnError(ilg), expressions);
        }
    }

    public class ConditionTreeNodeOr : ConditionTreeNode
    {
        private Or orFunction = Or.instance;
        private ConditionTreeNode[] expressions;

        public ConditionTreeNodeOr(ConditionTreeNode firstExpr, ConditionTreeNode sndExpr)
        {
            expressions = new ConditionTreeNode[2];
            expressions[0] = firstExpr;
            expressions[1] = sndExpr;
        }

        public override void CompileToDouble(ILGenerator ilg, Generate onSuccess)
        {
          // sestoft: changed
            orFunction.CompileToDouble(ilg, onSuccess, OnError(ilg), expressions);
        }
    }

    public class ConditionTreeLeaf : ConditionTreeNode
    {
        private readonly CGExpr expression;
        private readonly bool shouldBeTrue;


        public ConditionTreeLeaf(CGExpr expression, bool shouldBeTrue)
        {
            this.expression = expression;
            this.shouldBeTrue = shouldBeTrue;
        }

        public CGExpr Expression
        {
            get { return expression; }
        }

        public bool ShouldBeTrue
        {
            get { return shouldBeTrue; }
        }

        public override void CompileToDouble(ILGenerator ilg, Generate onSuccess)
        {
            if (ShouldBeTrue)
                CompileExpr(ilg, onSuccess);
            else
            {
                Generate convertFirst = new Generate(delegate
                                            {
                                                ilg.Emit(OpCodes.Ldc_R8, 0.0);
                                                ilg.Emit(OpCodes.Ceq);
                                                ilg.Emit(OpCodes.Conv_R8);
                                                onSuccess.Gen(ilg);
                                            });
                CompileExpr(ilg, convertFirst);
            }
        }

        private void CompileExpr(ILGenerator ilg, Generate onSuccess)
        {
            LocalBuilder var;
            if (ConditionResults.TryGetValue(Expression, out var))
            {
                ilg.Emit(OpCodes.Ldloc, var);
                onSuccess.Gen(ilg);
            }
            else
            {
              Expression.CompileToDouble(ilg, new Generate(delegate
                                                  {
                                                    var = doubleVarPool.GetVariable();
                                                    //TODO: MPO: this var has to be released at some stage again
                                                    //maybe this is already done?
                                                    ilg.Emit(OpCodes.Dup);
                                                    ilg.Emit(OpCodes.Stloc, var);
                                                    ConditionResults.Add(Expression, var);
                                                    onSuccess.Gen(ilg);
                                                  })
                                         , new Generate(delegate
                                               {
                                                 OnError(ilg).Gen(ilg);
                                                 ilg.Emit(OpCodes.Stloc, var);
                                               }));
            }
        }
    }
}
